// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of HTMLDOMTranslator
//
// HTMLDOMTranslator is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// HTMLDOMTranslator is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with HTMLDOMTranslator. If not, see <http://www.gnu.org/licenses/>.

//	Purpose: translating MSHTML to XML.

#include <htmldomtranslator.h>

#include <atlcomcli.h>
#include <atlstr.h>
#include <mshtml.h>
#include <msxml.h>


const long g_lHTMLElementType = 1;
const long g_lHTMLTextType = 3;
const long g_lHTMLCommentType = 8;

CHTMLDOMTranslator::CHTMLDOMTranslator(const CLSID &clsid)
:m_clsid(clsid)
{
}

CHTMLDOMTranslator::~CHTMLDOMTranslator(void)
{
}

HRESULT CHTMLDOMTranslator::cloneNode(IHTMLDOMNode * pHTML, IXMLDOMDocument ** ppXML, const bool bDeep) const
{
	CComPtr<IXMLDOMDocument> pFactoryDocument;
	HRESULT hr = pFactoryDocument.CoCreateInstance(m_clsid);
	if(pFactoryDocument)
	{
		CComPtr<IXMLDOMNode> pXMLNode;
		hr = internalTranslateHTMLToXML(pFactoryDocument,pHTML,&pXMLNode,bDeep);
		if(pXMLNode)
		{
			hr = pFactoryDocument->appendChild(pXMLNode,0);
			pFactoryDocument.CopyTo(ppXML);
		}
	}
	return hr;
}
// <summary>Translates the HTMLDOMNode  pHTML into an XML DOM Node ppXML</summary>
HRESULT CHTMLDOMTranslator::cloneNode(IHTMLDOMNode * pHTML, IXMLDOMNode ** ppXML, const bool bDeep) const
{
	CComPtr<IXMLDOMDocument> pFactoryDocument;
	HRESULT hr = pFactoryDocument.CoCreateInstance(m_clsid);
	if(pFactoryDocument)
	{
		hr = internalTranslateHTMLToXML(pFactoryDocument,pHTML,ppXML,  bDeep);
		//Add to the document to allow stylesheets to function.
		if(*ppXML)
		{
			DOMNodeType dt;
			(*ppXML)->get_nodeType(&dt);
			if(dt == NODE_ELEMENT)
				hr = pFactoryDocument->appendChild(*ppXML,0);
		}
#ifdef _DEBUG
//Debug stepin.
		if(false && *ppXML)
		{
			CComBSTR s;
			(*ppXML)->get_xml(&s);
			::MessageBoxW(0,s,0,0);
		}
#endif

	}
	return hr;
}
// <summary>Translates the HTMLDOMNode  pHTML into an XML DOM Node ppXML</summary>
//<param name="pDoc">XMLDOMDocument used as a factory</param>
HRESULT CHTMLDOMTranslator::internalTranslateHTMLToXML(IXMLDOMDocument * pDoc,IHTMLDOMNode * pHTML, IXMLDOMNode ** ppXML, const bool bDeep) const 
{
	HRESULT hr = E_FAIL;
	long lType;
	pHTML->get_nodeType(&lType);
	
	switch(lType)
	{
		case g_lHTMLTextType:
			hr = translateText(pDoc,pHTML,ppXML);
			break;
		case g_lHTMLElementType:
			hr = translateElement(pDoc,pHTML,ppXML, bDeep);			
			break;
		case g_lHTMLCommentType:
			hr = S_FALSE;
			break;
		default:
			hr = E_FAIL;
	}
	return hr;
}

HRESULT CHTMLDOMTranslator::translateElement(IXMLDOMDocument * pDoc,IHTMLDOMNode * pHTML, IXMLDOMNode ** ppXML, const bool bDeep) const
{
	
	CComBSTR sLocalName;
	HRESULT hr = pHTML->get_nodeName(&sLocalName);
	CComQIPtr<IHTMLElement2> pHTMLEl = pHTML;
	if(pHTMLEl)
	{
		CComBSTR sNSPrefix;
		CComBSTR sName;
		pHTMLEl->get_scopeName(&sNSPrefix);
		if(!sNSPrefix || sNSPrefix == L"HTML")
		{
			sName.Attach(sLocalName.Detach());
		}
		else
		{
			CStringW csName;
			csName.Format(L"%s:%s",sNSPrefix,sLocalName);
			sName.Attach(csName.AllocSysString());
		}
		CComQIPtr<IXMLDOMNode> pNode;
		CComPtr<IXMLDOMElement> pEl;
		if(pHTMLEl)
		{
			CComBSTR sURN;
			pHTMLEl->get_tagUrn(&sURN);
			
			hr = pDoc->createNode(CComVariant(NODE_ELEMENT),sName,sURN,&pNode);
			pEl = pNode;
		}
		else
		{
			hr = pDoc->createElement(sName,&pEl);
			pNode = pEl;
		}

		if(pNode && pEl)
		{
			copyAttributes(pHTML,pEl);
			if(bDeep)
				hr = copyChildren(pDoc,pHTML,pNode);
			pNode.CopyTo(ppXML);
		}
	}
	else
	{
		hr = E_INVALIDARG;
	}
	return hr;
}

HRESULT CHTMLDOMTranslator::copyChildren(IXMLDOMDocument * pDoc,IHTMLDOMNode * pHTML,IXMLDOMNode * pEl) const
{
	
	CComPtr<IDispatch> pDisp;
	HRESULT hr = pHTML->get_childNodes(&pDisp);
	CComQIPtr<IHTMLDOMChildrenCollection> pColl = pDisp;
	if(pColl)
	{
		long l;
		pColl->get_length(&l);
		for(long i = 0; i<l;++i)
		{
			CComPtr<IDispatch> pDispItem;
			pColl->item(i,&pDispItem);
			CComQIPtr<IHTMLDOMNode> pHTMLNode = pDispItem;
			CComPtr<IXMLDOMNode> pXMLNode;
			//Of course it's deep, you can't get here otherwise
			hr = internalTranslateHTMLToXML(pDoc,pHTMLNode,&pXMLNode,true);
			if(SUCCEEDED(hr))
			{
				pEl->appendChild(pXMLNode,0);
#ifdef _DEBUG
				if(false)
				{
					CComBSTR s;
					pEl->get_xml(&s);
					::MessageBoxW(0,s,0,0);
				}
#endif
			}
			else
				break;
		}
	}	
	return hr;
}

void CHTMLDOMTranslator::copyAttributes(IHTMLDOMNode * pHTML,IXMLDOMElement * pEl) const
{
	CComPtr<IDispatch> pDisp;
	pHTML->get_attributes(&pDisp);
	CComQIPtr<IHTMLAttributeCollection> pColl = pDisp;
	if(pColl)
	{
		long l;
		pColl->get_length(&l);
		CComPtr<IDispatch> pAttrDispatch;
		CComQIPtr<IHTMLDOMAttribute> pAttr;
		for(CComVariant vtIx(--l);vtIx.lVal > -1; --vtIx.lVal)
		{
			pColl->item(&vtIx,&pAttrDispatch);
			pAttr = pAttrDispatch;
			if(pAttr)
			{
				VARIANT_BOOL vtb;
				pAttr->get_specified(&vtb);
				if(vtb)
				{
					CComBSTR sName;
					pAttr->get_nodeName(&sName);
					CComVariant vtValue;
					pAttr->get_nodeValue(&vtValue);
					pEl->setAttribute(sName,vtValue);
				}
			}
			pAttrDispatch.Release();
			pAttr.Release();
		}

		//Stupid MSHTML pretends that there is no style attribute, copy the style property, too.
		//	This includes not only the content of teh style attribute, but also anything set by "element.style="
		//	in script.  In the context in which we are using it, this should make no difference.
		//	It will not contain any style properties that have been applied from the cascade.
		//
		//First, check that a proper style attribute has not already been translated across
		//	in case we are using some clever gecko wrapper or mysterious future version of IE.
		const CComBSTR sStyle(L"style");
		CComVariant vStyleValue;
		pEl->getAttribute(sStyle,&vStyleValue);
		if(vStyleValue.vt != VT_BSTR)
		{
			CComQIPtr<IHTMLElement> pHTMLEl(pHTML);
			if(pHTMLEl)
			{
				CComPtr<IHTMLStyle> pStyle;
				pHTMLEl->get_style(&pStyle);
				if(pStyle)
				{
					CComBSTR sCSSText;
					pStyle->get_cssText(&sCSSText);
					//Only bother to add it if it exists.  This may be slightly inaccurate, as the source
					//	may have included style="", in more situations, it will be accurate, as an element with no style
					//	attribute will yield an empty string from the above call.
					if(!!sCSSText)
					{
						CComVariant vCSSText(sCSSText);
						pEl->setAttribute(sStyle,vCSSText);
					}
				}
			}
		}
	}
}

//<summary>uses pDoc to create an XML text node and returns it in ppXML</summary>
HRESULT CHTMLDOMTranslator::translateText(IXMLDOMDocument * pDoc,IHTMLDOMNode * pHTML, IXMLDOMNode ** ppXML) const 
{
	CComVariant vtVal;
	HRESULT hr = pHTML->get_nodeValue(&vtVal);
	if(SUCCEEDED(hr) && VT_BSTR == vtVal.vt)
	{
		CComPtr<IXMLDOMText> pDomText;
		hr = pDoc->createTextNode(vtVal.bstrVal,&pDomText);
		if(pDomText)
		{
			hr = pDomText->QueryInterface(IID_IXMLDOMNode,reinterpret_cast<void**>(ppXML));
		}
	}
	return hr;
}	
